/* 
 * This file is part or JMathLib 
 * 
 * Check it out at http://www.jmathlib.de
 *
 * Author:   
 * (c) 2008-2009   
 */
package joctave.core.graphics;

import joctave.core.interpreter.ErrorLogger;
import joctave.core.graphics.properties.*;
import joctave.core.graphics.axes.*;
import joctave.ui.awt.AboutGUIDialog;

import java.awt.*;
import java.awt.event.*;
import java.awt.print.*;

/** created and holds the axes of a plot, there can be multiple axes in one plot*/
public class FigureObject extends GraphicalObject implements WindowListener, Printable, ActionListener, PropertyListener
{  
	private class FigurePanel extends Panel
	{
		FigurePanel()
		{
			super();
		}

		public void update(Graphics g)
		{
            ErrorLogger.debugLine("FigureOb: update");
			paint(g);
		}

		public void add(AxesObject ax)
		{
			super.add(ax.getComponent());
		}

		public void add(AxesObject ax, int index)
		{
			super.add(ax.getComponent(), index);
		}
	}

	/**The number of this figure*/
	private int figureNo = 0;

	/** number of tiles in horizontal direction*/
	private int gridX = 1;

	/** number of tiles in vertical direction*/
	private int gridY = 1;

	/**An array of the axes*/	
	private AxesObject[][] figureElements = new AxesObject[gridY][gridX];

	/**The current axes where all plot commands will be directed to */
	private int currentAxes = 1;

	/**A pointer to the graphics manager*/
	private GraphicsManager gM;

	// size of frame;
	private int dyFrame = 500;
	private int dxFrame = 500;

	/*Holds all elements of a figure. The panel is organized as a grid*/
	private Frame       fig = new Frame();
	private FigurePanel p   = new FigurePanel();

	// the menu bar
	MenuBar mbar = new MenuBar();
	
    // properties
	public DoubleVectorProperty AlphamapP  = new DoubleVectorProperty(this, "Alphamap", new double[0], -1);
    public BooleanProperty BeingDeletedP = new BooleanProperty(this, "BeingDeleted", false);
    public FunctionalHandleProperty CloseRequestFcnP = new FunctionalHandleProperty(this, "CloseRequestFcn", "");
	public ColorProperty ColorP = new ColorProperty(this, "Color", Color.lightGray);
	public ColormapProperty ColormapP = new ColormapProperty(this, "Colormap");
	public FunctionalHandleProperty CreateFcnP = new FunctionalHandleProperty(this, "CreateFcn", "");
	// public CurrentAxesP
	public StringProperty  CurrentCharacterP = new StringProperty(this, "CurrentCharacter", "");
	// public CurrentObjectP
	// public CurrentPointP
    public FunctionalHandleProperty DeleteFcnP = new FunctionalHandleProperty(this, "DeleteFcn", "");
	public BooleanProperty DockControlsP = new BooleanProperty(this, "DockControls", true);
	public BooleanProperty DoubleBufferP = new BooleanProperty(this, "DoubleBuffer", true);
    public StringProperty  FileNameP = new StringProperty(this, "FileName", "");
    public ColormapProperty FixedColorsP = new ColormapProperty(this, "FixedColors");
	public BooleanProperty IntegerHandleP = new BooleanProperty(this, "IntegerHandle", true);
	public BooleanProperty InvertHardcopyP = new BooleanProperty(this, "InvertHardcopy", true);
    public FunctionalHandleProperty KeyPressFcnP = new FunctionalHandleProperty(this, "KeyPressFcn", "");
    public FunctionalHandleProperty KeyReleaseFcnP = new FunctionalHandleProperty(this, "KeyReleaseFcn", "");
    public RadioProperty MenuBarP = new RadioProperty(this, "MenuBar", new String[] {"none", "figure"}, "figure");
    public DoubleProperty MinColormapP = new DoubleProperty(this, "MinColormap", 1.0);
    public StringProperty NameP = new StringProperty(this, "Name", "");
    public RadioProperty NextPlotP = new RadioProperty(this, "NextPlot", new String[] {"next", "add", "replace", "replacechildren"}, "add");
	public BooleanProperty NumberTitleP = new BooleanProperty(this, "NumberTitle", true);
    public RadioProperty PaperOrientationP = new RadioProperty(this, "PaperOrientation", new String[] {"portrait", "landscape"}, "portrait");
    public DoubleVectorProperty PaperPositionP  = new DoubleVectorProperty(this, "PaperPosition", new double[0], -1);
    public RadioProperty PaperPositionModeP = new RadioProperty(this, "PaperPositionMode", new String[] {"auto", "manual"}, "manual");
    public DoubleVectorProperty PaperSizeP  = new DoubleVectorProperty(this, "PaperSize", new double[2], -1);
    public StringProperty PaperTypeP = new StringProperty(this, "PaperType", "");
    public RadioProperty PaperUnitsP = new RadioProperty(this, "PaperUnits", new String[] {"normalized", "inches", "centimeters", "points"}, "inches");
    public RadioProperty PointerP = new RadioProperty(this, "Pointer", 
             new String[] {"crosshair", "arrow", "watch", "topl", "topr", 
                           "botl", "botr", "circle", "cross", "fleur", "left", 
                           "right", "top", "bottom", "fullcrosshair", "ibeam", 
                           "custom"}, "arrow");
    public DoubleVectorProperty PointerShapeCDataP  = new DoubleVectorProperty(this, "PointerShapeCData", new double[16], -1);
    public DoubleVectorProperty PointerShapeHotSpotP  = new DoubleVectorProperty(this, "PointerShapeHotSpot", new double[2], -1);
    public DoubleVectorProperty PositionP  = new DoubleVectorProperty(this, "Position", new double[4], 4);
    public RadioProperty RendererP = new RadioProperty(this, "Renderer", new String[] {"painters", "zbuffer", "OpenGL"}, "painters");
    public RadioProperty RendererModeP = new RadioProperty(this, "RendererMode", new String[] {"auto", "manual"}, "auto");
	public BooleanProperty ResizeP = new BooleanProperty(this, "Resize", true);
    public FunctionalHandleProperty ResizeFcnP = new FunctionalHandleProperty(this, "ResizeFcn", "");
    public RadioProperty SelectionTypeP = new RadioProperty(this, "SelectionType", new String[] {"normal", "extend", "alt", "open"}, "normal");
	public BooleanProperty ShareColorsP = new BooleanProperty(this, "ShareColors", true);
    public RadioProperty ToolBarP = new RadioProperty(this, "ToolBar", new String[] {"none", "auto", "figure"}, "auto");
    // public UIContextMenuP
	public UnitsProperty UnitsP = new UnitsProperty(this, "Units", "pixels");
    public FunctionalHandleProperty WindowButtonDownFcnP = new FunctionalHandleProperty(this, "WindowButtonDownFcn", "");
    public FunctionalHandleProperty WindowButtonMotionFcnP = new FunctionalHandleProperty(this, "WindowButtonMotionFcn", "");
    public FunctionalHandleProperty WindowButtonUpFcnP = new FunctionalHandleProperty(this, "WindowButtonUpFcn", "");
    public FunctionalHandleProperty WindowScrollWheelFcnP = new FunctionalHandleProperty(this, "WindowScrollWheelFcn", "");
    public RadioProperty WindowStyleP = new RadioProperty(this, "WindowStyle", new String[] {"normal", "modal", "docked"}, "normal");
    // WVisual
    public RadioProperty WVisualModeP = new RadioProperty(this, "WVisualMode", new String[] {"auto", "manual"}, "manual");
    // XDisplay
    // XVisual
    public RadioProperty VisualModeP = new RadioProperty(this, "VisualMode", new String[] {"auto", "manual"}, "manual");
    

    public FigureObject() 
    {  
        // set type to "axes"
        TypeP = new TypeProperty(this, "figure");

        MenuBarP.addPropertyListener(this);
        NameP.addPropertyListener(this);
    }
    
    public FigureObject(int _figureNo)
	{  
        this();
        
        // set type to "axes"
	    TypeP = new TypeProperty(this, "figure");

        figureNo = _figureNo;

		// setup window frame
		fig.setSize(dxFrame, dyFrame);
		fig.setTitle("Figure No."+figureNo);
		fig.setBackground( ColorP.getColor() );
		fig.addWindowListener(this);

		// create initial axes object
		figureElements[0][0] = new AxesObject();

		// setup internal layout
		p.setLayout(new GridLayout(gridY, gridX));
		p.add(figureElements[0][0]);
		fig.add(p);
        fig.setResizable(ResizeP.isSet());

		// other initialization
		initMenuBar();
		fig.setVisible(true);
	}

    /**
     * set up menue for figure window
     *
     */
	private void initMenuBar()
	{
		

		Menu fileMenu = new Menu("File");
		fileMenu.add(new MenuItem("New Figure", new MenuShortcut(KeyEvent.VK_N, false)));
		fileMenu.add(new MenuItem("Close", new MenuShortcut(KeyEvent.VK_W, false)));
		fileMenu.add("Close All");
		fileMenu.addSeparator();
		fileMenu.add(new MenuItem("Print...", new MenuShortcut(KeyEvent.VK_P, false)));
		fileMenu.addActionListener(this);
		mbar.add(fileMenu);
		
		//Menu editMenu = new Menu("Edit");
		//mbar.add(editMenu);

		//Menu viewMenu = new Menu("View");
		//mbar.add(viewMenu);

		//Menu insertMenu = new Menu("Insert");
		//mbar.add(insertMenu);

		//Menu toolsMenu = new Menu("Tools");
		//mbar.add(toolsMenu);

		//Menu helpMenu = new Menu("Help");
		//helpMenu.add("Check for Updates");
		//helpMenu.add("About JOctave");
		//mbar.add(helpMenu);

		fig.setMenuBar(mbar);
	}

	//public void addAxes(AxesObject _axes) 
	//{
		//figureElements.addElement(_axes);
		//currentAxes = figureElements.indexOf(_axes);
		//ErrorLogger.debugLine("FigureObject: addElement: "+currentAxes);
	//}

	public AxesObject getCurrentAxes() 
	{
		// numbering starts with 1 (top left) increases to the right
		// 1 | 2 | 3 | 4
		// 5 | 6 | 7 | 8
		int y = (int)(currentAxes / gridX);
		if (y*gridX == currentAxes) y--;

		int x = (int)(currentAxes - y*gridX) - 1;
		if (figureElements[y][x] == null)
			figureElements[y][x] = new AxesObject();

		return figureElements[y][x];
	}

    public PolarAxesObject getCurrentPolarAxes() 
    {
        ErrorLogger.debugLine("FigureObject: getcurrentpolaraxes");
        // numbering starts with 1 (top left) increases to the right
        // 1 | 2 | 3 | 4
        // 5 | 6 | 7 | 8
        int y = (int)(currentAxes / gridX);
        if (y*gridX == currentAxes) y--;

        int x = (int)(currentAxes - y*gridX) - 1;

        if (figureElements[y][x] instanceof PolarAxesObject)
            return (PolarAxesObject)figureElements[y][x];
        else if (figureElements[y][x] == null)
            figureElements[y][x] = new PolarAxesObject();
        else
            figureElements[y][x] = new PolarAxesObject();
        
        p.remove(currentAxes-1);
        p.add(figureElements[y][x], currentAxes-1);
        p.validate();

        return (PolarAxesObject)figureElements[y][x];
    }

	public void convertCurrentAxesTo3DAxes()
	{
		//ErrorLogger.debugLine("FigureObject: convertCurrentAxesTo3DAxes");

		if (getCurrentAxes() instanceof Axes3DObject) return;

		// somehow remove following 4 lines      
		int y = (int)(currentAxes / gridX);
		if (y*gridX == currentAxes) y--;

		int x = (int)(currentAxes - y*gridX) - 1;

		figureElements[y][x]= new Axes3DObject();

		p.remove(currentAxes-1);
		p.add(figureElements[y][x], currentAxes-1);
		p.validate();
	}

	public int getFigureNumber()
	{
		return figureNo;
	}

	/**devide the current figure into n*m tiles*/
	public void setSubPlot(int _gridY, int _gridX, int _currentAxes)
	{
		// numberring starts with 1 (top left) increases to the right
		// 1 | 2 | 3 | 4
		// 5 | 6 | 7 | 8   (example of setSubPlot(2,4,x))
		//ErrorLogger.debugLine("Figure:setSubPlot: ");

		if (_currentAxes<= _gridY*_gridX)	currentAxes = _currentAxes;
		else  							    currentAxes = 1;

		// check if only currentAxes is changed
		if ((gridY==_gridY) && (gridX==_gridX)) return;

		// gridY and/or gridX is changed => make new layout
		gridY       = _gridY;
		gridX       = _gridX;

		p.removeAll();
		p.setLayout(new GridLayout(gridY,gridX));
		figureElements = new AxesObject[gridY][gridX];

		for (int y=0; y<figureElements.length; y++)
		{
			for (int x=0; x<figureElements[y].length; x++)
			{
				//ErrorLogger.debugLine("FigureObject: subplot : "+y+" "+x);
				//if (figureElements[y][x]!=null)
				figureElements[y][x] = new AxesObject();
				p.add(figureElements[y][x]);
			}	
		}

		p.validate();
	}

    /**
     *
     */
    public void clearFigure()
    {
        p.removeAll();
        p.setLayout(new GridLayout(1,1));

        for (int y=0; y<figureElements.length; y++)
        {
            for (int x=0; x<figureElements[y].length; x++)
            {
                //ErrorLogger.debugLine("FigureObject: subplot : "+y+" "+x);
                //if (figureElements[y][x]!=null)
                figureElements[y][x] = null;
            }   
        }
        p.validate();
        p.repaint();
    }
    
	/**
	 * method for printing the graphics window to an external printer
	 */
	public int print(Graphics g, PageFormat pf, int page)  throws PrinterException
	{
		//ErrorLogger.debugLine("FigureObject: print : page="+page );

		// get printable area
		int yPos = (int)pf.getImageableY();
		int xPos = (int)pf.getImageableX();
		int yMax = (int)pf.getImageableHeight();
		int xMax = (int)pf.getImageableWidth();
		//ErrorLogger.debugLine("FigureObject: xPos="+xPos+" yPos="+yPos);
		//ErrorLogger.debugLine("FigureObject: xMax="+xMax+" yMax="+yMax);

		double rPage = (double)yMax/(double)xMax;
		double rFig = (double)fig.getSize().height/(double)fig.getSize().width;

		if (rPage > rFig)
		{
			yMax = (int)(rFig*xMax);
			yPos += ((int)pf.getImageableHeight()-yMax)/2;
		}
		else
		{
			xMax = (int)(yMax/rFig);
			xPos += ((int)pf.getImageableWidth()-xMax)/2;
		}

		// draw black rectangle around printing area
		g.setColor(Color.black);
		g.drawRect(xPos, yPos, xMax, yMax);

		//this.setBackground(Color.lightGray);    

		// create a new panel and group all elements which need to be printed
		//  -> is is done in order to get the correct size of the elements
		FigurePanel pP = new FigurePanel();
		pP.setLayout(new GridLayout(gridY, gridX));
		for (int y=0; y<figureElements.length; y++)
		{
			for (int x=0; x<figureElements[y].length; x++)
			{
				//ErrorLogger.debugLine("FigureObject: print : "+y+" "+x);
				pP.add(figureElements[y][x]);
			}    
		}

		// needed?
		pP.setSize(xMax,yMax);
		//pP.setBounds(xPos,yPos,xMax,yMax);

		// move origin of graphical context to left top corner
		//  of the visible page layout
		g.translate(xPos, yPos);

		int xTransSum=0;
		int yTransSum=0;
		int xTrans=0;
		int yTrans=0;

		// print axes on the printer-page
		for (int y=0; y<figureElements.length; y++)
		{
			yTrans=0;
			for (int x=0; x<figureElements[y].length; x++)
			{
				//ErrorLogger.debugLine("FigureObject: print-method : "+y+" "+x);
				//ErrorLogger.debugLine("figure:size x="+figureElements[y][x].getSize().width+" y="+figureElements[y][x].getSize().height);

				figureElements[y][x].getComponent().setSize(xMax, yMax);
				xTrans = figureElements[y][x].getSize().width;
				yTrans = Math.max(yTrans, figureElements[y][x].getSize().height);

				figureElements[y][x].setBackBuffer(false);
				figureElements[y][x].paint(g);
				figureElements[y][x].setBackBuffer(true);

				// move origin to next figure element
				g.translate(xTrans, 0);
				xTransSum += xTrans;
			}    

			// move origin back to left side and 
			// move origin down one row
			g.translate(-xTransSum, 0);
			g.translate(0, yTrans);

			// remember moving down
			yTransSum += yTrans;

		}

		// go back to origin "top left"
		g.translate(0, -yTransSum);


		// copy figure elements back to original panel on the screen
		for (int y=0; y<figureElements.length; y++)
		{
			for (int x=0; x<figureElements[y].length; x++)
			{
				//ErrorLogger.debugLine("FigureObject: copy back : "+y+" "+x);
				//if (figureElements[y][x]!=null)
				p.add(figureElements[y][x]);
			}    
		}

		// return one valid page (page-no. 0)
		if (page==0) 
			return PAGE_EXISTS;
		else
			return NO_SUCH_PAGE;
	}

	public void close()
	{
		close(true);
	}

	public void close(boolean notify)
	{
		if (notify)
			gM.removeFigure(figureNo);
		fig.dispose();
	}

	public void windowClosing(WindowEvent e)
	{
		//ErrorLogger.debugLine("FigureObject: windowClosing: "+figureNo);
		this.close();
	}

	public void windowActivated(WindowEvent e){}

	public void windowDeactivated(WindowEvent e){}

	public void windowClosed(WindowEvent e){}

	public void windowIconified(WindowEvent e){}

	public void windowDeiconified(WindowEvent e){}

	public void windowOpened(WindowEvent e){}

	public void setGraphicsManager(GraphicsManager _gM)
	{
		gM = _gM;
	}

	public void actionPerformed(ActionEvent ev)
	{
		String cmd = ev.getActionCommand();
		if (cmd.equals("Close"))
			gM.closeFigure(figureNo);
		else if (cmd.equals("New Figure"))
			gM.createNewFigure();
		else if (cmd.equals("Close All"))
			gM.closeAll();
		else if (cmd.equals("Print..."))
			doPrint();
		//else if (cmd.equals("About JOctave")) {
		//	AboutGUIDialog aboutDialog = new AboutGUIDialog(this);
		//}
	}

	private void doPrint()
	{
		PrinterJob job = PrinterJob.getPrinterJob();
		job.setPrintable(this);
		if (job.printDialog())
		{
			try
			{
				job.print();
			}
			catch (PrinterException e)
			{
			}
		}
	}

    public void repaint()
    {
        //super.repaint();
    }
    
    public void propertyChanged(Property p)
    {
        ErrorLogger.debugLine("FigureObject property changed: "+ p.getName());

        
        // xxx
        if (p==DockControlsP)
        {
            if (DockControlsP.isSet())
            {
                
            }
            else
            {
                
            }
        }
        

        // xxx
        if (p==DoubleBufferP)
        {
            if (DoubleBufferP.isSet())
            {
                
            }
            else
            {
                
            }
        }

        // display/remove menu bar
        if (p == MenuBarP)
        {
            if (MenuBarP.is("none"))
                fig.setMenuBar(null);
            else
                fig.setMenuBar(mbar);
        }

        // change name of this figure
        if (p == NameP)
        {
            if (NameP.getString().equals(""))
                fig.setTitle("Figure No."+figureNo);
            else
                fig.setTitle("Figure No."+figureNo+": "+NameP.getString());
        }

        parent.repaint();
    }

}
